/****************************************************************************************
 * File:			Factory.cs
 * Description:		Class for creating module derived classes from a string.
 ***************************************************************************************/

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.IO;

namespace XGAE.Framework
{
    /// <summary>
    /// Template class for creation of Modules derived types.  The base class is a singleton, 
    /// containing a registry of derived classes.  Each derived class can create and delete 
    /// custom modules.  So when the static instance is asked to create a Module, it scans the 
    /// registry and calls the appropriate derived module. The class is templatised to support
    /// multiple static instances for different variations of modules (Module, Backend).
    /// </summary>
    class Factory : IDisposable
    {
        #region Fields

        private static Factory singleton;

        public static Factory Singelton
        {
            get { return singleton; } 
        }

        private Dictionary<string, Factory>	registry; // list of registered classes

        public Dictionary<string, Factory> Registry
        {
            get { return registry; }
        }

        private Dictionary<Component, Factory> components; // store factory responsible for each instance

        public Dictionary<Component, Factory> Components
        {
            get { return components; }
        }

        #endregion

        #region C'tors

        /// <summary>
        /// Default constructor.
        /// </summary>
        private Factory() { }

        /// <summary>
        /// Add class name and type pair to registry of known classes.
        /// Registers the instance inside the singleton's registry.
        /// </summary>
        /// <param name="type">The type of the module provided by the factory.</param>
        protected Factory( string type )
        {
            Factory.Singelton.registry[type] = this;
        }

        /*/// <summary>
        /// Virtual destructor since specific factories are derived from this class.
        /// </summary>
        public virtual ~Factory()
        {
            foreach (Factory f in registry)
	        {
		        f = null;
	        }
        }*/
        
        #region IDisposable Methods

        // Track whether Dispose has been called.
        private bool disposed = false;

        // Implement IDisposable.
        // Do not make this method virtual.
        // A derived class should not be able to override this method.
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
        // If disposing equals false, the method has been called by the
        // runtime from inside the finalizer and you should not reference
        // other objects. Only unmanaged resources can be disposed.
        protected virtual void Dispose(bool disposing)
        {
            // Check to see if Dispose has already been called.
            if (!this.disposed)
            {
                // If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.
                    foreach (KeyValuePair<string, Factory> kvp in registry)
                    {
                        kvp.Value.Dispose();
                    }
                }

                // Note disposing has been done.
                disposed = true;
            }
        }

        // Use C# destructor syntax for finalization code.
        // This destructor will run only if the Dispose method
        // does not get called.
        // It gives your base class the opportunity to finalize.
        // Do not provide destructors in types derived from this class.
        ~Factory()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of
            // readability and maintainability.
            Dispose(false);
        }

        #endregion

        #endregion

        #region Public Methods

        /// <summary>
        /// Public method for Component creation.
        /// </summary>
        /// <param name="type">Name of the class to construct</param>
        /// <returns>Reference to the created Module.</returns>
        public static Component Create(string type)
        {
            if (!Factory.Singelton.Registry.ContainsKey(type))
            {
                //throw Exception( type + std::string(" is an unknown module."), __FILE__, __LINE__ );
            }

            Component component = Factory.Singelton.Registry[type].CreateComponent();

            if (component == null)
            {
                //throw Exception("Failed to create component " + type + ".", __FILE__, __LINE__ );
            }

            Factory.Singelton.Components[component] = Factory.Singelton.Registry[type];
            return component;
        }
    
        /// <summary>
        /// Public method for Component deletion. Scan registry and remove an instance from memory.
        /// </summary>
        /// <param name="module">Reference to the created Module.</param>
        public static void Delete(Component component)
        {
            if (!Factory.Singelton.Components.ContainsKey(component))
            {
                //throw Exception("Unknown component cannot delete.", __FILE__, __LINE__);
            }

            Factory.Singelton.Components[component].DeleteComponent(component);
            Factory.Singelton.Components.Remove(component);
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Factory component creator.  Must be overriden by derived factories, but is 
        /// defined (not pure virtual) due to the singleton with the registry.
        /// </summary>
        /// <returns>Reference to a new instance.</returns>
        protected virtual Component CreateComponent()
        {
	        //throw fear::Exception( "CreateComponent() called for an uninstanciated factory.", __FILE__, __LINE__ );
	        return null;
        }

        /// <summary>
        /// Delete this module.  Must be overriden by derived factories too.
        /// </summary>
        /// <param name="component">Reference to the created Module.</param>
        protected virtual void DeleteComponent( Component component )
        {
	        //throw fear::Exception( "DeleteComponent() called for an uninstanciated factory.", __FILE__, __LINE__ );
        }

        #endregion
    }

    public delegate Component Alloc();
    public delegate void Free(Component c);

    /// <summary>
    /// Factory instance for loading modules from arbitrary DLL files.
    /// </summary>
    class DllFactory : Factory, IDisposable
    {
        #region Fields     

        private Assembly assembly;
        private Type type;
        private Alloc alloc; //function used to create new modules
        private Free free; //function used to free modules

        private string moduleType; // type of module
        private string filename; // name of the DLL file
        private int counter; // reference counting for laziness

        /// <summary>
	    /// Check if the DLL loaded succesfully. 
	    /// </summary>
	    /// <returns></returns>
	    public bool IsLoaded
	    {
            get { return (assembly != null); }
        }

        #endregion

        #region C'tors

        /// <summary>
        /// String construct for derived factories.
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="name"></param>
        public DllFactory(string filename, string name) : base (name)
        {
            this.moduleType = name;
	        this.filename = filename;
            this.counter = 0;
            this.assembly = null;          
        }

        /*/// <summary>
        /// Virtual destructor.
        /// </summary>
        public virtual ~DllFactory()
        {
            Close();
        }*/

        #region IDisposable Methods

        // Track whether Dispose has been called.
        private bool disposed = false;

        // Implement IDisposable.
        // Do not make this method virtual.
        // A derived class should not be able to override this method.
        public void Dispose()
        {
            Dispose(true);
            // This object will be cleaned up by the Dispose method.
            // Therefore, you should call GC.SupressFinalize to
            // take this object off the finalization queue
            // and prevent finalization code for this object
            // from executing a second time.
            GC.SuppressFinalize(this);
        }

        // Dispose(bool disposing) executes in two distinct scenarios.
        // If disposing equals true, the method has been called directly
        // or indirectly by a user's code. Managed and unmanaged resources
        // can be disposed.
        // If disposing equals false, the method has been called by the
        // runtime from inside the finalizer and you should not reference
        // other objects. Only unmanaged resources can be disposed.
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);

            // Check to see if Dispose has already been called.
            if (!this.disposed)
            {
                /*// If disposing equals true, dispose all managed
                // and unmanaged resources.
                if (disposing)
                {
                    // Dispose managed resources.
                    this.Close();
                }*/

                this.Close();
                // Note disposing has been done.
                disposed = true;
            }
        }

        // Use C# destructor syntax for finalization code.
        // This destructor will run only if the Dispose method
        // does not get called.
        // It gives your base class the opportunity to finalize.
        // Do not provide destructors in types derived from this class.
        ~DllFactory()
        {
            // Do not re-create Dispose clean-up code here.
            // Calling Dispose(false) is optimal in terms of
            // readability and maintainability.
            Dispose(false);
        }

        #endregion

        #endregion

        #region Overrided Methods

        /// <summary>
        /// Build a module from the functions provided by the DLL
        /// </summary>
        /// <returns>The module created.</returns>
        protected override Component CreateComponent()
        {
            this.Open();
	        if (assembly==null)
	        {
		        //Warning("Can't initialise dynamic library " + filename);
		        return null;
	        }
	        ++counter;
            return this.alloc();
        }

	    /// <summary>
        /// Destroy a module from the functions provided by the DLL.
	    /// </summary>
        /// <param name="module">The module in question.</param>
        protected override void DeleteComponent(Component module)
        {
            this.free(module);
            if (--this.counter == 0)
                this.Close();
        }

        #endregion

        #region Private Methods

        /// <summary>
        /// Load the DLL and find the create/delete procedures.
        /// </summary>
        private void Open()
        {
            if (assembly != null)
                return;

            try
            {
                assembly = Assembly.LoadFrom(this.filename);
                type = assembly.GetType("XGAE.Framework.Modules." + moduleType);
            }
            catch (FileNotFoundException)
            {
                //Console.WriteLine("Could not load Assembly: \"{0}\"", args[0]);
                return;
            }
            catch (TypeLoadException)
            {
                //Console.WriteLine("Could not load Type: \"{0}\"\nfrom assembly: \"{1}\"", args[1], args[0]);
                return;
            }

            // Get the methods from the type
            MethodInfo[] tmpMethods = type.GetMethods();
            MethodInfo[] methods = new MethodInfo[2];

            if (methods == null)
            {
                Console.WriteLine("No Matching Types Found");
                return;
            }

            if (assembly != null)
            {
                int methodsCount = tmpMethods.Length;
                for(int i = 0; i<methodsCount; i++)
                {
                    if (tmpMethods[i].Name == "CreateComponent")
                        methods[0] = tmpMethods[i];
                    else if(tmpMethods[i].Name == "DeleteComponent")
                        methods[1] = tmpMethods[i];
                }

                this.alloc = delegate()
                {
                    return (Component)this.AttemptMethod(type, methods[0], methods[0].Name, null);
                };

                this.free = delegate(Component component)
                {
                    this.AttemptMethod(type, methods[1], methods[1].Name, new Component[] { component });
                };               

                if (alloc == null || free == null)
                {
                    /*Warning("Cannot find CreateComponent() or DeleteComponent() in " + filename + 
                        ".  Make sure to compile with the _DLL symbol defined, and use the same " +
                        "configuration as the FEAR platform. (Debug Mode is recommended.)");*/
                    // error occurred, free the library
                    this.Close();
                }
            }
        }

        /// <summary>
        /// Free the DLL handles so the files can be replaced without shutting down the server.
        /// </summary>
        private void Close()
        {
            //if there wasn't an error in the loading
            if (assembly != null)
            {
                // ask windows to free the library
                ;

                assembly = null;
            }
        }

        // Checks a method for a signature match, and invokes it if there is one
		private Object AttemptMethod(Type type, MethodInfo method, String name, Component[] args)
		{
			// Name does not match?
			if (String.Compare(method.Name, name, false, System.Globalization.CultureInfo.InvariantCulture) != 0)
			{
				//throw new CustomException(method.DeclaringType + "." + method.Name + ": Method Name Doesn't Match!");
			}

			// Wrong number of parameters?
			ParameterInfo[] param = method.GetParameters();

			if (param.Length != args.Length)
			{
				//throw new CustomException(method.DeclaringType + "." + method.Name + ": Method Signatures Don't Match!");
			}

			// Ok, can we convert the strings to the right types?
			Object[] newArgs = new Object[args.Length];

			/*for (int index = 0; index < args.Length; index++)
			{
				try
				{
                    newArgs[index] = Convert.ChangeType(args[index], param[index].ParameterType, System.Globalization.CultureInfo.InvariantCulture);
				}
				catch (Exception e)
				{
					//throw new CustomException(method.DeclaringType + "." + method.Name + ": Argument Conversion Failed", e);
				}				
			}*/

			// We made it this far, lets see if we need an instance of this type
			Object instance = null;

			if (!method.IsStatic)
			{
				instance = Activator.CreateInstance(type);
			}

			// ok, let's invoke this one!
            return method.Invoke(instance, args);
		}

        #endregion
    }
}
